<?php

namespace app\common\utils;

use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Exception;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ZipArchive;

/**
 * 操作文件相关
 */
class FileUtil
{
    /**
     * 导出Excel文件
     * @param array $list 数据列表数组
     * @param array $field_map 数组字段数组
     * @param string $file_path 文件路径
     * @param string $filename 文件名称
     * @param bool $filename_append_now_time 文件名是否追加当前时间
     * @param string $suffix 文件后缀
     * @return false|string
     * @throws Exception
     */
    public static function outputExcel(array $list, array $field_map, string $file_path, string $filename, bool $filename_append_now_time = true, string $suffix = 'xlsx')
    {
        //处理 $list 数据列表
        $data = [];
        foreach ($list ?? [] as $v) {
            $item = [];
            foreach ($field_map as $field_key => $field_val) {
                //解析 $field_map 中 数组多级key的数据,需要在 $field_map 中定义多个key 以.分隔, 例如: user.info.name
                $field_key_split = explode('.', $field_key);
                $container = $v;
                for ($index = 0; $index <= 2; $index++) {
                    if (isset($field_key_split[$index]) && isset($container[$field_key_split[$index]])) {
                        $container = $container[$field_key_split[$index]];
                    }
                }
                if (is_array($container)) {
                    $container = '';
                }
                $item[$field_key] = $container;
            }
            //$data[] = array_values($item);
            $data[] = $item;
        }

        //表头A-Z列定义
        $cellName = [];
        for ($i = 'A'; $i <= 'Z'; $i++) {
            $cellName[] = $i;
        }
        $spreadsheet = new Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();

        //设置表头行
        $titleList = array_values($field_map);
        //设置表头单元格样式
        $styleArray = [
            'font' => [
                'bold' => true, // 字体加粗
                'color' => [
                    'rgb' => '0000ff',
                ],
            ]
        ];
        foreach ($titleList as $tkey => $tval) {
            $sheet->setCellValue($cellName[$tkey] . '1', $tval);
            $sheet->getStyle($cellName[$tkey] . '1')->applyFromArray($styleArray);
        }

        //设置表数据内容行和列
        foreach ($data ?? [] as $key => $val) {
            $val = array_values($val);
            foreach ($val as $kk => $vv) {
                if (is_string($vv)) { //对于字符串类型,防止较长的数字字符串被格式化为科学计数法
                    $sheet->setCellValueExplicitByColumnAndRow(($kk + 1), ($key + 2), $vv, DataType::TYPE_STRING);
                } else {
                    //导出的某个字段值里面包含换行符(\n)会导致报错,因此需要过滤前后的空格和换行符
                    $vv=trim($vv);
                    //导出的数据中如果存在 "=" 开头的字符串,会出错 Formula Error: An unexpected error occurred",因此需要对 "=" 特殊处理
                    if (strpos($vv, '=') === 0 ) {
                        $vv = "'" . $vv; // 在 = 前面加个单引号
                    }
                    $sheet->setCellValue($cellName[$kk] . ($key + 2), $vv);
                }
            }
        }

        //创建目录并写入Excel文件
        if (!is_dir($file_path)) {
            if (!mkdir($file_path, 0777, true)) {
                return false;
            }
        }
        if ($filename_append_now_time) {
            $filename .= '-' . date("YmdHis", time());
        }
        $filename .= '.' . strtolower($suffix);
        $objWriter = new Xlsx($spreadsheet);
        $objWriter->save($file_path . '/' . $filename);

        return $filename;
    }

    /**
     * 根据URL地址下载文件
     * @param $url
     * @param $savePath
     * @return void
     */
    public static function downloadRemoteFile($url, $savePath)
    {
        $file_folder = dirname($savePath);
        if (!file_exists($file_folder)) {
            mkdir($file_folder, 0777, true);
        }

        ob_start();
        readfile($url);
        $img = ob_get_contents();
        ob_end_clean();
        $size = strlen($img);
        $fp = fopen($savePath, 'a');
        fwrite($fp, $img);
        fclose($fp);
    }

    /**
     * 压缩整个文件夹
     */
    public static function createZipFile($dir_path, $filename = null, $delete_dir = true)
    {
        if (!$filename) {
            $filename = $dir_path . '.zip';
        }

        // 创建一个新的ZIP文件
        $zip = new ZipArchive();

        // 如果压缩包创建成功
        if (!$zip->open($filename, ZipArchive::CREATE | ZipArchive::OVERWRITE) === true) {
            throw new \Exception("压缩包创建失败", 2001);
        }

        $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_path), RecursiveIteratorIterator::LEAVES_ONLY);
        foreach ($files as $name => $file) {
            // Skip directories (they would be added automatically)
            if (!$file->isDir()) {
                // Get real and relative path for current file
                $filePath = $file->getRealPath();
                $relativePath = substr($filePath, strlen($dir_path) + 1);
                //echo $relativePath . PHP_EOL;
                // Add current file to archive
                $zip->addFile($filePath, $relativePath);
            }
        }

        // 关闭ZIP文件
        $zip->close();

        //删除原本的文件夹
        if ($delete_dir) {
            self::deleteFolder($dir_path);
        }

        return true;
    }

    /**
     * 删除非空目录
     * @param $folderPath
     * @return void
     */
    public static function deleteFolder($folderPath)
    {
        if (is_dir($folderPath)) {
            $files = array_diff(scandir($folderPath), array('.', '..'));
            foreach ($files as $file) {
                $filePath = $folderPath . '/' . $file;
                is_dir($filePath) ? self::deleteFolder($filePath) : unlink($filePath);
            }
            rmdir($folderPath);
        }
    }

    /**
     * 递归获取一个目录下的所有文件，穿透子目录
     * @param string $dir 目录路径
     * @param bool $is_recursion 是否递归获取子目录
     * @return array
     */
    public static function readAllFiles(string $dir, bool $is_recursion = false): array
    {
        if (!is_dir($dir)) {
            return [];
        }

        $list = [];
        $files = scandir($dir);
        foreach ($files as $file) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $path = $dir . DIRECTORY_SEPARATOR . $file;
            if (is_file($path)) {
                $list[] = $path;
                continue;
            }
            if ($is_recursion && is_dir($path)) {
                $list = array_merge($list, self::readAllFiles($path));
            }
        }
        return $list;
    }

    /**
     * 从一个包含路径的文件地址中提取文件名称
     * @param string $path
     * @return string
     */
    public static function getFileNameByPath(string $path): string
    {
        $file_array = explode(DIRECTORY_SEPARATOR, $path);
        return $file_array[count($file_array) - 1];
    }

}
